package dong.common;

import com.hp.webfluxclient.api.RestHandler;
import dong.annoaction.ApiServer;
import dong.bean.MethodInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;

/**
 * 使用jdk动态代理代理类
 *
 * @author dongxing
 */
@Slf4j
public class JDKProxyCreator implements ProxyCreator {

    @Override
    public Object createProxy(Class<?> clazz) {
        log.info("createProxy: {}", clazz);

        // 得到接口创建api服务器
        ServerInfo serverInfo = extractServerInfo(clazz);

        log.info("serverInfo: {}", serverInfo);

        // 给每一个代理类一个实例
        RestHandler restHandler = new WebClientRestHandler();

        // 初始化服务器信息
        restHandler.init(serverInfo);

        return Proxy.newProxyInstance(this.getClass().getClassLoader(),
                new Class[]{clazz},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args)
                            throws Throwable {

                        // 根据方法和参数得到调用的信息
                        MethodInfo methodInfo = extractMethodInfo(method, args);

                        log.info("methodInfo: {}", methodInfo);

                        // 调用rest
                         return restHandler.invokeRest(serverInfo, methodInfo);

                    }
                });
    }

    /**
     * 根据方法定义和调用参数得到相对应的信息
     *
     * @param method
     * @param args
     * @return
     */
    private MethodInfo extractMethodInfo(Method method, Object[] args) {
        MethodInfo methodInfo = MethodInfo.builder().build();

        extractUrlAndMethod(method, methodInfo);
        extractRequestParams(method, args, methodInfo);

        // 得到返回的数据类型
        extractReturnInfo(method, methodInfo);

        return methodInfo;
    }

    private void extractReturnInfo(Method method, MethodInfo methodInfo) {


        // 返回flux还是Mono
        // isAssignableFrom判断类型是否是某个类的子类
        // instanceof判断实例是否是某个子类
        boolean assignableFrom = method.getReturnType().isAssignableFrom(Flux.class);
        methodInfo.setReturnFlux(assignableFrom);


        // 返回对象的实例类型
        Class<?> elementType = extractElementType(method.getGenericReturnType());
        methodInfo.setReturnElementType(elementType);

    }

    private Class<?> extractElementType(Type genericReturnType) {

        Class<?> actualTypeArgument = (Class<?>) ((ParameterizedType) genericReturnType).getActualTypeArguments()[0];
        return actualTypeArgument;


    }

    /**
     * 得到请求的参数
     *
     * @param method
     * @param args
     * @param methodInfo
     */
    private void extractRequestParams(Method method, Object[] args, MethodInfo methodInfo) {

        // 得到body
        Parameter[] parameters = method.getParameters();

        // 参数和值对应的map
        Map<String, Object> map = new LinkedHashMap<>();

        methodInfo.setParams(map);

        for (int i = 0; i < parameters.length; i++) {

            // 是否带@PathVariable注解
            PathVariable annotation = parameters[i].getAnnotation(PathVariable.class);

            if (Optional.ofNullable(annotation).isPresent()) {
                map.put(annotation.value(), args[i]);
            }

            // 是否带了@RequestBody
            RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);

            if (Optional.ofNullable(requestBody).isPresent()) {
                methodInfo.setBody(((Mono<?>) args[i]));

                //请求对象的实际类型
                methodInfo.setBodyElementType(extractElementType(parameters[i].getParameterizedType()));
            }
        }
    }

    /**
     * 提取注解中的信息
     *
     * @param clazz
     * @return
     */
    public ServerInfo extractServerInfo(Class<?> clazz) {
        ApiServer annotation = clazz.getAnnotation(ApiServer.class);
        return ServerInfo.builder().url(annotation.value()).build();
    }

    /**
     * 得到请求的url和方法
     *
     * @param method
     * @param methodInfo
     */
    private void extractUrlAndMethod(Method method, MethodInfo methodInfo) {
        Annotation[] annotations = method.getAnnotations();

        for (Annotation annotation : annotations) {
            if (annotation instanceof GetMapping) {

                // get
                GetMapping a = ((GetMapping) annotation);

                methodInfo.setUrl(a.value()[0]).setHttpMethod(HttpMethod.GET);
            } else if (annotation instanceof PostMapping) {

                // post
                PostMapping p = ((PostMapping) annotation);

                methodInfo.setUrl(p.value()[0]).setHttpMethod(HttpMethod.POST);
            } else if (annotation instanceof DeleteMapping) {

                // del
                DeleteMapping p = ((DeleteMapping) annotation);

                methodInfo.setUrl(p.value()[0]).setHttpMethod(HttpMethod.DELETE);
            } else if (annotation instanceof PutMapping) {

                // put
                PutMapping p = ((PutMapping) annotation);

                methodInfo.setUrl(p.value()[0]).setHttpMethod(HttpMethod.PUT);
            }
        }
    }
}
